/*
    Copyright (C) 2019 Zongming Liu <1849059316@qq.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "ProgramStateCache.h"
#include "../base/Debuger.h"
#include "../utility/utility.h"
#include "../base/GameWorld.h"
#include "../render/IDirectRender.h"
#include "../render/IRender.h"

namespace SkyEngine2d
{

    static ID3D11Device *getD3dDevice()
    {
        return GameWorld::getInstance()->getDirectInterface()->getD3dDevice();
    }

    ProgramStateCache::ProgramStateCache()
        : m_blendState(std::make_unique<BlendState>()), m_depthStencilState(std::make_unique<DepthStencilState>()), m_samplerState(std::make_unique<SamplerState>()), m_rasterizerState(std::make_unique<RasterizerState>())
    {
        m_device = getD3dDevice();
    }

    //////////////////////////////////深度缓存状态////////////////////////////////////////
    static auto createDepthstencilState(ID3D11Device *device, bool enable, bool writeEnable, ID3D11DepthStencilState **pResult)
    {
        DebugerAssert(device, "ID3D11Device 为空");

        D3D11_DEPTH_STENCIL_DESC desc = {};

        desc.DepthEnable = enable ? TRUE : FALSE;
        desc.DepthWriteMask = writeEnable ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
        desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;

        desc.StencilEnable = FALSE;
        desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
        desc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;

        desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
        desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;

        desc.BackFace = desc.FrontFace;

        return device->CreateDepthStencilState(&desc, pResult);
    }

    ID3D11DepthStencilState *DepthStencilState::DepthNone() const
    {
        static ID3D11DepthStencilState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(createDepthstencilState(getD3dDevice(), false, false, &s));
        return s;
    }

    ID3D11DepthStencilState *DepthStencilState::DepthDefault() const
    {
        static ID3D11DepthStencilState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(createDepthstencilState(getD3dDevice(), true, true, &s));
        return s;
    }

    ID3D11DepthStencilState *DepthStencilState::DepthRead() const
    {
        static ID3D11DepthStencilState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(createDepthstencilState(getD3dDevice(), true, false, &s));
        return s;
    }

    //////////////////////////////// 混合 //////////////////////////////////////////////
    static auto CreateBlendState(ID3D11Device *device, D3D11_BLEND srcBlend, D3D11_BLEND destBlend, _Out_ ID3D11BlendState **pResult)
    {
        D3D11_BLEND_DESC desc = {};

        desc.RenderTarget[0].BlendEnable = (srcBlend != D3D11_BLEND_ONE) ||
                                           (destBlend != D3D11_BLEND_ZERO);

        desc.RenderTarget[0].SrcBlend = srcBlend;
        desc.RenderTarget[0].SrcBlendAlpha = srcBlend;
        desc.RenderTarget[0].DestBlend = destBlend;
        desc.RenderTarget[0].DestBlendAlpha = destBlend;
        desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
        desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;

        desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

        return device->CreateBlendState(&desc, pResult);
    }

    ID3D11BlendState *BlendState::Opaque() const
    {
        static ID3D11BlendState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(CreateBlendState(getD3dDevice(), D3D11_BLEND_ONE, D3D11_BLEND_ZERO, &s));
        return s;
    }

    ID3D11BlendState *BlendState::AlphaBlend() const
    {
        static ID3D11BlendState *s = nullptr;
        if (s)
        {
            return s;
        }

        checkFailed(CreateBlendState(getD3dDevice(), D3D11_BLEND_ONE, D3D11_BLEND_INV_SRC_ALPHA, &s));
        return s;
    }

    ID3D11BlendState *BlendState::Additive() const
    {
        static ID3D11BlendState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(CreateBlendState(getD3dDevice(), D3D11_BLEND_SRC_ALPHA, D3D11_BLEND_ONE, &s));
        return s;
    }

    ID3D11BlendState *BlendState::NonPremultiplied() const
    {
        static ID3D11BlendState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(CreateBlendState(getD3dDevice(), D3D11_BLEND_SRC_ALPHA, D3D11_BLEND_INV_SRC_ALPHA, &s));
        return s;
    }

    //////////////////////////////采样状态///////////////////////////////
    static auto CreateSamplerState(ID3D11Device *device, D3D11_FILTER filter, D3D11_TEXTURE_ADDRESS_MODE addressMode, _Out_ ID3D11SamplerState **pResult)
    {
        D3D11_SAMPLER_DESC desc = {};

        desc.Filter = filter;

        desc.AddressU = addressMode;
        desc.AddressV = addressMode;
        desc.AddressW = addressMode;

        desc.MaxAnisotropy = (device->GetFeatureLevel() > D3D_FEATURE_LEVEL_9_1) ? D3D11_MAX_MAXANISOTROPY : 2;

        desc.MaxLOD = FLT_MAX;
        desc.ComparisonFunc = D3D11_COMPARISON_NEVER;

        return device->CreateSamplerState(&desc, pResult);
    }

    ID3D11SamplerState *SamplerState::PointWrap() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }

        CreateSamplerState(getD3dDevice(), D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_WRAP, &s);
        return s;
    }

    ID3D11SamplerState *SamplerState::PointClamp() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }
        CreateSamplerState(getD3dDevice(), D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_CLAMP, &s);
        return s;
    }

    ID3D11SamplerState *SamplerState::LinearWrap() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }
        CreateSamplerState(getD3dDevice(), D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_WRAP, &s);
        return s;
    }

    ID3D11SamplerState *SamplerState::LinearClamp() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }

        CreateSamplerState(getD3dDevice(), D3D11_FILTER_MIN_MAG_MIP_LINEAR, D3D11_TEXTURE_ADDRESS_CLAMP, &s);
        return s;
    }

    ID3D11SamplerState *SamplerState::AnisotropicWrap() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }

        CreateSamplerState(getD3dDevice(), D3D11_FILTER_ANISOTROPIC, D3D11_TEXTURE_ADDRESS_WRAP, &s);
        return s;
    }

    ID3D11SamplerState *SamplerState::AnisotropicClamp() const
    {
        static ID3D11SamplerState *s = nullptr;
        if (s)
        {
            return s;
        }

        CreateSamplerState(getD3dDevice(), D3D11_FILTER_ANISOTROPIC, D3D11_TEXTURE_ADDRESS_CLAMP, &s);
        return s;
    }

    /////////////////////////////////光栅化状态////////////////////////////////////

    static auto CreateRasterizerState(ID3D11Device *device, D3D11_CULL_MODE cullMode, D3D11_FILL_MODE fillMode, ID3D11RasterizerState **pResult)
    {
        D3D11_RASTERIZER_DESC desc = {};

        desc.CullMode = cullMode;
        desc.FillMode = fillMode;
        desc.DepthClipEnable = TRUE;
        desc.MultisampleEnable = TRUE;

        return device->CreateRasterizerState(&desc, pResult);
    }

    ID3D11RasterizerState *RasterizerState::CullNone() const
    {
        static ID3D11RasterizerState *s = nullptr;
        if (s)
        {
            return s;
        }

        checkFailed(CreateRasterizerState(getD3dDevice(), D3D11_CULL_NONE, D3D11_FILL_SOLID, &s));
        return s;
    }

    ID3D11RasterizerState *RasterizerState::CullClockwise() const
    {
        static ID3D11RasterizerState *s = nullptr;
        if (s)
        {
            return s;
        }
        checkFailed(CreateRasterizerState(getD3dDevice(), D3D11_CULL_FRONT, D3D11_FILL_SOLID, &s));
        return s;
    }

    ID3D11RasterizerState *RasterizerState::CullCounterClockwise() const
    {
        static ID3D11RasterizerState *s = nullptr;
        if (s)
        {
            return s;
        }

        checkFailed(CreateRasterizerState(getD3dDevice(), D3D11_CULL_BACK, D3D11_FILL_SOLID, &s));
        return s;
    }

    ID3D11RasterizerState *RasterizerState::Wireframe() const
    {
        static ID3D11RasterizerState *s = nullptr;
        if (s)
        {
            return s;
        }

        checkFailed(CreateRasterizerState(getD3dDevice(), D3D11_CULL_NONE, D3D11_FILL_WIREFRAME, &s));
        return s;
    }
}
